<html>
  <body>
    <canvas id="canvas" width=960 height=1080></canvas>
    <script type="module">
        const video = document.createElement('video');
        video.src = './vc/teddy1_vp9_640x360_30fps.webm';
        video.loop = true;
        video.muted = true;

        // Test with two videos. The original implementation had a bug
        // where importing a second video would result in improper
        // synchronization. The mailbox would be used before it was created.
        const video2 = document.createElement('video');
        video2.src = './vc/teddy2_vp9_320x180_15fps.webm';
        video2.loop = true;
        video2.muted = true;

        const adapter = navigator.gpu && await navigator.gpu.requestAdapter();
        const device = await adapter.requestDevice();

        const canvas = document.getElementById('canvas');
        const context = canvas.getContext('gpupresent');

        const swapChainFormat = 'bgra8unorm';
        const swapChain = context.configureSwapChain({
          device,
          format: swapChainFormat,
        });

        const pipeline = device.createRenderPipeline({
          vertexStage: {
            module: device.createShaderModule({
              code: `
[[builtin(vertex_index)]] var<in> VertexIndex : u32;

[[location(0)]] var<out> fragUV : vec2<f32>;
[[builtin(position)]] var<out> Position : vec4<f32>;

[[stage(vertex)]]
fn main() -> void {
  const pos : array<vec3<f32>, 6> = array<vec3<f32>, 6>(
    vec3<f32>( 1.0,  1.0, 0.0),
    vec3<f32>( 1.0, -1.0, 0.0),
    vec3<f32>(-1.0, -1.0, 0.0),
    vec3<f32>( 1.0,  1.0, 0.0),
    vec3<f32>(-1.0, -1.0, 0.0),
    vec3<f32>(-1.0,  1.0, 0.0)
  );
  const uv : array<vec2<f32>, 6> = array<vec2<f32>, 6>(
    vec2<f32>(1.0, 0.0),
    vec2<f32>(1.0, 2.0),
    vec2<f32>(0.0, 2.0),
    vec2<f32>(1.0, 0.0),
    vec2<f32>(0.0, 2.0),
    vec2<f32>(0.0, 0.0)
  );
  Position = vec4<f32>(pos[VertexIndex], 1.0);
  fragUV = uv[VertexIndex];
}
`,
            }),
            entryPoint: 'main',
          },
          fragmentStage: {
            module: device.createShaderModule({
              code: `
[[binding(0), group(0)]] var mySampler: sampler;
[[binding(1), group(0)]] var myTexture: texture_2d<f32>;
[[binding(2), group(0)]] var myTexture2: texture_2d<f32>;

[[location(0)]] var<in> fragUV : vec2<f32>;
[[location(0)]] var<out> outColor : vec4<f32>;

[[stage(fragment)]]
fn main() -> void {
  if (fragUV.y <= 1.0) {
    outColor = textureSample(myTexture, mySampler, fragUV);
  } else {
    var uv : vec2<f32> = vec2<f32>(fragUV.x, fragUV.y - 1.0);
    outColor = textureSample(myTexture2, mySampler, uv);
  }
}
`,
            }),
            entryPoint: 'main',
          },
          primitiveTopology: 'triangle-list',
          colorStates: [
            {
              format: swapChainFormat,
            },
          ],
        });

        const linearSampler = device.createSampler({
          magFilter: 'linear',
          minFilter: 'linear',
        });

        const renderPassDescriptor = {
          colorAttachments: [
            {
              attachment: undefined, // Assigned later
              loadValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
            },
          ],
        };

        function frame() {
          renderPassDescriptor.colorAttachments[0].attachment = swapChain
            .getCurrentTexture()
            .createView();

          const videoTexture = device.experimentalImportTexture(
            video, GPUTextureUsage.SAMPLED);

          const videoTexture2 = device.experimentalImportTexture(
            video2, GPUTextureUsage.SAMPLED);

          const bindGroup = device.createBindGroup({
            layout: pipeline.getBindGroupLayout(0),
            entries: [
              {
                binding: 0,
                resource: linearSampler,
              },
              {
                binding: 1,
                resource: videoTexture.createView(),
              },
              {
                binding: 2,
                resource: videoTexture2.createView(),
              },
            ],
          });

          const commandEncoder = device.createCommandEncoder();
          const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
          passEncoder.setPipeline(pipeline);
          passEncoder.setBindGroup(0, bindGroup);
          passEncoder.draw(6);
          passEncoder.endPass();
          device.queue.submit([commandEncoder.finish()]);

          // Destroy the texture after submit to promptly recycle resources.
          videoTexture.destroy();
          videoTexture2.destroy();

          requestAnimationFrame(frame);
        }

        await video.play();
        await video2.play();
        requestAnimationFrame(frame);
    </script>
  </body>
</html>